package com.lhczf.lucenedb.threads;

import com.lhczf.lucenedb.service.DataHub;
import com.lhczf.lucenedb.service.LuceneDbServer;
import com.lhczf.lucenedb.service.ServerContext;
import com.lhczf.lucenedb.service.ZipServer;
import com.lhczf.lucenedb.util.FileUtils;
import com.lhczf.lucenedb.util.LuceneUtil;
import com.lhczf.lucenedb.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

@Slf4j
public class IntegrationIndexThread extends Thread {

    private List<String> allThreadIndexDirs = new ArrayList<>();
    private LocalDateTime firstDateTime;
    private DataHub dataHub;

    public IntegrationIndexThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        ServerContext serverContext = SpringUtil.getBean(ServerContext.class);
        this.dataHub = SpringUtil.getBean(DataHub.class);
        IndexWriter indexWriter = serverContext.getIntegrWriter();
        String indexRoot = SpringUtil.getProperValue("system.lucenedb.index.root");
        String overtime = SpringUtil.getProperValue("system.lucene.thread.overtime");
        for (;;) {
            String indexDir = getMessage();
            if (allThreadIndexDirs.isEmpty() && StringUtils.hasLength(indexDir)) {
                firstDateTime = LocalDateTime.now();
            }
            if (firstDateTime == null) {
                continue;
            }
            allThreadIndexDirs.add(LuceneDbServer.INDEX_DAYS_DIR + File.separator + indexDir);
            LocalDateTime curreantDateTime = LocalDateTime.now();
            Duration duration = Duration.between(firstDateTime, curreantDateTime);
            long minutes = duration.toMinutes();
            if (allThreadIndexDirs.size() == serverContext.getThreadTotal()) {
                log.info("正常切换和合并索引.");
                allThreadIndexDirs.clear();
                begingIntegration(indexWriter, indexRoot, null);
            } else if (minutes >= Long.parseLong(overtime)) {
                List<File> dealData = new ArrayList<>();
                for (String dir : allThreadIndexDirs) {
                    dealData.add(new File(indexRoot + File.separator + dir));
                }
                allThreadIndexDirs.clear();
                log.warn("异常切换和合并索引.");
                begingIntegration(indexWriter, indexRoot, dealData);
            }
        }
    }

    private String getMessage() {
        try {
            return dataHub.getMessageQueue().poll(2000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error("", e);
            Thread.currentThread().interrupt();
        }
        return null;
    }

    private void begingIntegration(IndexWriter indexWriter, String indexRoot, List<File> dealData) {
        List<Directory> directoryList = new ArrayList<>();
        List<Path> paths = new ArrayList<>();
        LuceneUtil luceneUtil = SpringUtil.getBean(LuceneUtil.class);

        if (dealData == null) {
            dealData = new ArrayList<>();
            String dirData = LocalDateTime.now().minusDays(1).format(LuceneDbServer.Y_M_D_DTF);
            StringBuilder sb = new StringBuilder(indexRoot);
            sb.append(File.separator).append(LuceneDbServer.INDEX_DAYS_DIR)
                    .append(File.separator).append(dirData);
            File datas = new File(sb.toString());
            List<File> indexDatas = FileUtils.findDirs(datas);
            dealData.addAll(indexDatas);
        }
        for (File dir : dealData) {
            Directory directory = luceneUtil.getDirectory(dir);
            directoryList.add(directory);
            paths.add(dir.toPath());
        }

        List<File> minusDir = minusDirs(dealData);
        //尝试合并异常的目录下的数据
        dealThreadDirIndex(minusDir, directoryList, paths);

        LocalDateTime start = LocalDateTime.now();
        log.info("开始进行索引的合并前的备份操作。");
        try {
            zipIndexData(paths);
        } catch (IOException e) {
            log.error("", e);
        }
        LocalDateTime end = LocalDateTime.now();
        Duration duration = Duration.between(start, end);
        log.info("索引的合并前的备份操作完成，耗时：{}分钟。", duration.toMinutes());

        integration(indexWriter, directoryList);
        dellIndex(paths);
        start = LocalDateTime.now();
        duration = Duration.between(end, start);
        firstDateTime = null;
        log.info("结束索引的合并整理操作。总共耗时：{}分钟。", duration.toMinutes());
    }

    private void dealThreadDirIndex(List<File> minusDir, List<Directory> directoryList, List<Path> paths) {
        LuceneUtil luceneUtil = SpringUtil.getBean(LuceneUtil.class);
        for (File file : minusDir) {
            Directory directory = luceneUtil.getDirectory(file);
            if (directory != null) {
                directoryList.add(directory);
                paths.add(file.toPath());
            }
        }
    }

    private List<File> minusDirs(List<File> dealData) {
        List<File> minusDir = new ArrayList<>();
        String topDir = dealData.get(0).getParent();
        List<File> indexDatas = FileUtils.findDirs(new File(topDir));
        for (File file : indexDatas) {
            boolean exist = false;
            for (File data : dealData) {
                if (file.getName().equals(data.getName())) {
                    exist = true;
                }
            }
            if (!exist) {
                minusDir.add(file);
            }
        }
        return minusDir;
    }

    private void zipIndexData(List<Path> paths) throws IOException {
        ZipServer zipServer = SpringUtil.getBean(ZipServer.class);
        zipServer.initZipInfoAndZip(paths);
    }

    private void dellIndex(List<Path> paths) {
        for (Path path : paths) {
            try (Stream<Path> pathStream = Files.walk(path)) {
                pathStream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
            } catch (IOException e) {
                log.error("", e);
            }
        }
        FileUtils.deleteFile(paths.get(0).getParent().toFile());
    }

    private void integration(IndexWriter indexWriter, List<Directory> directoryList) {
        Directory[] directories = new Directory[directoryList.size()];
        int index = 0;
        for (Directory directory : directoryList) {
            directories[index++] = directory;
        }
        try {
            //合并index
            indexWriter.addIndexes(directories);
            indexWriter.commit();
        } catch (IOException e) {
            log.error("", e);
        }
    }
}
